﻿using System;
using System.Threading;

namespace Quail.Parallel
{
    /// <summary>
    /// Provides the ability to easily run a test in a separate thread
    /// </summary>
    public class SimpleParallelTest
    {
        private readonly Func<ITestShell> _createShell;
        private readonly Action _testMethod;

        /// <summary>
        /// Initializes a new instance of the <see cref="SimpleParallelTest"/> class.
        /// </summary>
        /// <param name="createShell">The create shell.</param>
        /// <param name="testMethod">The test method.</param>
        public SimpleParallelTest(Func<ITestShell> createShell, Action testMethod)
        {
            _createShell = createShell;
            _testMethod = testMethod;
        }

        /// <summary>
        /// Starts a test using the specified test shell and task.
        /// </summary>
        /// <param name="shell">The test shell.</param>
        /// <param name="parentTask">The parent task.</param>
        /// <returns></returns>
        public SimpleParallelTestRun StartTest(ITestShell shell, Task parentTask)
        {
            var result = new SimpleParallelTestRun(this, shell, parentTask);
            result.Start();

            return result;
        }

        /// <summary>
        /// Class to manage a parallel test run
        /// </summary>
        public class SimpleParallelTestRun
        {
            private readonly SimpleParallelTest _owner;
            private readonly ITestShell _shell;
            private readonly Task _parentTask;
            private Thread _thread;

            internal SimpleParallelTestRun(SimpleParallelTest owner, ITestShell shell, Task parentTask)
            {
                _owner = owner;
                _shell = shell;
                _parentTask = parentTask;
            }

            /// <summary>
            /// Gets a value indicating whether this test is running.
            /// </summary>
            /// <value>
            /// 	<c>true</c> if this instance is running; otherwise, <c>false</c>.
            /// </value>
            public bool IsRunning
            {
                get
                {
                    return !Join(0);
                }
            }

            /// <summary>
            /// Waits for the test to complete
            /// </summary>
            public void Join()
            {
                if (_thread != null)
                    _thread.Join();
            }

            /// <summary>
            /// Waits for the test to complete
            /// </summary>
            /// <param name="millisecondsTimeout">how long to wait for the test to complete</param>
            /// <returns>True if the test is complete</returns>
            public bool Join(int millisecondsTimeout)
            {
                return _thread == null || _thread.Join(millisecondsTimeout);
            }

            /// <summary>
            /// Starts the test (must not be running!)
            /// </summary>
            public void Start()
            {
                if (IsRunning)
                    throw new SimpleParallelTestException("Cannot start a test run that is already running");

                _thread = new Thread( () =>
                                          {
                                              var mgr = new ParallelTestManager<ITestShell, int>(_shell, 2)
                                              {
                                                  CreateParallelTestShell = idx => _owner._createShell(),
                                                  UseRootShellAsWorker = false,
                                                  TestMethod = (ts, unused) => _owner._testMethod()
                                              };
                                              mgr.EnqueueWorkItem(0);
                                              mgr.RunTests();
                                          } );

                _thread.Start();
            }
        }

        /// <summary>
        /// Exceptions in the SimpleParallelTest class
        /// </summary>
        public class SimpleParallelTestException : LogStatusException
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SimpleParallelTestException"/> class.
            /// </summary>
            /// <param name="msg">The MSG.</param>
            public SimpleParallelTestException(string msg)
                : base(msg)
            {
            }
        }
    }
}
